﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace TEAA
{
    public class BufferManager
    {
        int bufferPageSize = 8192;
        private static uint MAX_PAGES = 65536; //65536*8192 = 512MB
        public static uint DIRECTORY_PAGEINFO_BYTES = 2; //USHORT, max 65535
        private static BufferManager bufferManager;
        private SystemManager systemManager;

        private struct Page  //ezt azért raktam ide, mert csak ez az osztály használja, és mert nem kell akkor hozzáadjak új file-t :D
        {                    // Ez a lemezről beolvasott oldal, nem ugyanaz, mint az adatbázis oldalai (csak nagyjából)
            public string table;
            public uint pageNumber;
            public Boolean modified;
            //public Boolean referenceCount;
            public DateTime lastAccessed;
            public byte[] value;
            public int bufferPageIndex;
        }
        

        private List<Page> pageBuffer;

        private BufferManager()
        {
            systemManager = SystemManager.GetSystemManager();
            bufferPageSize = SystemProperties.BufferPageSize;
            pageBuffer = new List<Page>();
        }

        
        public static BufferManager GetBufferManager()
        {
            if (bufferManager == null)
                bufferManager = new BufferManager();
            return bufferManager;
        }


        /// <summary>
        /// Lekérdezi az utolsó oldalt, amit akár létre is hoz
        /// </summary>
        /// <param name="table">a táblázat</param>
        /// <param name="newpage">egyúttal létrehozzon egy új oldalt, vagy sem</param>
        
        public BinaryWriter getBinaryWriter(string table, uint pageNumber)
        {
           Page page = pageBuffer.Find(
             delegate(Page p)
             {
                 return (p.table == table) && (p.pageNumber == pageNumber);
             }
             );
            if (page.table != null)
            {
                page.lastAccessed = DateTime.Now;
                updatebufferPage(page);
            }
            else
            {
                page = getFreeBufferPage();
                page.lastAccessed = DateTime.Now;
                page.table = table;
                page.pageNumber = pageNumber;
                readPage(page);                    
            }

            return new BinaryWriter(new MemoryStream(page.value));
        

        }

        public BinaryReader getBinaryReader(string table, uint pageNumber)
        {
            Page page = pageBuffer.Find(
              delegate(Page p)
              {
                  return (p.table == table) && (p.pageNumber == pageNumber);
              }
              );
            if (page.table != null)
            {
                page.lastAccessed = DateTime.Now;
            }
            else
            {
                page = getFreeBufferPage();
                page.lastAccessed = DateTime.Now;
                page.table = table;
                page.pageNumber = pageNumber;
                readPage(page);                    
            }
            return new BinaryReader(new MemoryStream(pageBuffer[page.bufferPageIndex].value));

        }

        private void readPage(Page page)
        {
            byte[] result = new byte[bufferPageSize];
            string tableFile = systemManager.GetTablePath(page.table);
            using (FileStream fs = new FileStream(tableFile, FileMode.Open, FileAccess.Read))
            {
                fs.Seek(bufferPageSize * page.pageNumber, SeekOrigin.Begin);
                fs.Read(result, 0, bufferPageSize);
                fs.Close();
            }

            page.lastAccessed = DateTime.Now;
            page.value = result;
            updatebufferPage(page);

        }

        public bool appendNewPage(string table, uint pageNumber)
        {
            Page page = getFreeBufferPage();            
            page.table = table;
            page.pageNumber = pageNumber;
            page.value = new byte[SystemProperties.BufferPageSize];
            pageBuffer[page.bufferPageIndex] = page;
            return true;
           // throw new NotImplementedException();
            //uint newPageNo = 0;
            //return newPageNo;

        }

    /*
        public byte[] getPage(string table, uint pageNumber)
        {
            return getDiskPage(table,findDiskPage(table, pageNumber));
        }

       */ 
   /*     
        private ushort findDiskPage(string table, uint pageNumber)
        {
            ushort diskPage;
            BinaryReader directoryStream;

            byte[] directoryPage = getDiskPage(table, 0);
            while (pageNumber > (bufferPageSize / DIRECTORY_PAGEINFO_BYTES - 1))
            {
                directoryStream = new BinaryReader(new MemoryStream(directoryPage));
                directoryStream.BaseStream.Seek(bufferPageSize - DIRECTORY_PAGEINFO_BYTES, SeekOrigin.Begin);
                diskPage = directoryStream.ReadUInt16();
                directoryPage = getDiskPage(table, diskPage);
                pageNumber = pageNumber - (uint)bufferPageSize / DIRECTORY_PAGEINFO_BYTES + 1;
            }

            directoryStream = new BinaryReader(new MemoryStream(directoryPage));
            directoryStream.BaseStream.Seek(pageNumber * DIRECTORY_PAGEINFO_BYTES, SeekOrigin.Begin);
            return directoryStream.ReadUInt16();
        }

        private byte[] getDiskPage(string table, ushort pageNumber)
        {
            Page page = pageBuffer.Find(
             delegate(Page p)
             {
                 return (p.table == table) && (p.pageNumber == pageNumber);
             }
             );
            if (page.table != null)
            {
                page.lastAccessed = DateTime.Now;
                updatebufferPage(page);
                return page.value;
            }
            else
            {
                byte[] result = new byte[bufferPageSize];
                //string tableFile = SystemProperties.SystemRootLocation + Path.DirectorySeparatorChar + systemManager.GetdatabaseName()
                //    + systemManager.GetdatabaseName() + Path.DirectorySeparatorChar
                //    + Path.DirectorySeparatorChar + table + ".bin"; //ideiglenes, majd a systemManager -től fogja lekérdezni
                string tableFile = systemManager.GetTablePath(table);
                using (FileStream fs = new FileStream(tableFile, FileMode.Open, FileAccess.Read))
                {   
                    fs.Seek(bufferPageSize * pageNumber, SeekOrigin.Begin);
                    fs.Read(result, 0, bufferPageSize);
                    fs.Close();
                }
                page = getFreeBufferPage();
                page.lastAccessed = DateTime.Now;
                page.table = table;
                page.pageNumber = pageNumber;
                page.value = result;
                updatebufferPage(page);
                return page.value;
            }
        }
        */
        public void setUpdated(string table, uint pageNumber)
        {
            
            Page page = pageBuffer.Find(
             delegate(Page p)
             {
                 return (p.table == table) && (p.pageNumber==pageNumber);
             }
             );
            if (page.table != null)
            {
                page.modified = true;
                updatebufferPage(page);
            }
            else
            {
                throw new Exception("Page to update not found.");
            }
            
        }

        private Page getFreeBufferPage()
        {
            if (pageBuffer.Count <= MAX_PAGES)
            {

                Page page = new Page();
                page.bufferPageIndex = pageBuffer.Count;
                page.value = new byte[SystemProperties.BufferPageSize];
                pageBuffer.Add(page);
                return pageBuffer[pageBuffer.Count-1];
                
            }
            else
            {
                throw new NotImplementedException("Page replacement Not Yet Implemented");
            }

        }

        private void updatebufferPage(Page page)
        {
            pageBuffer[page.bufferPageIndex] = page;

        }


        private void flush(Page page)
        {

            //flush
            // időzített kiírás - mind
            // gombnyobásra - mind
            // mielőtt kidobódik a pufferből, csak akkor kell kiirni, mielott kidobom
            //

            //string tableFile = SystemProperties.SystemRootLocation + Path.DirectorySeparatorChar + systemManager.GetdatabaseName()
            //        + Path.DirectorySeparatorChar + page.table + ".bin"; //ideiglenes, majd a systemManager -től fogja lekérdezni
            string tableFile = systemManager.GetTablePath(page.table);
            using (FileStream fs = new FileStream(tableFile, FileMode.Open, FileAccess.Write))
            {
                fs.Seek(bufferPageSize * page.pageNumber, SeekOrigin.Begin);
                fs.Write(page.value, 0, bufferPageSize);
                fs.Close();
            }
            page.modified = false;
        }

        public void flushAll()
        {
            foreach (Page p in pageBuffer)
            {
                if (p.modified) flush(p);
            }
            
        }
    }
}
